package com.nage.engine;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import android.app.Activity;
import android.hardware.SensorManager;
import android.util.Log;

import com.nage.components.Component;

public class GameGraph {

	ArrayList<GameObject> graph;
	int lastObject = -1;
	ArrayList<GameObject> addQueue;
	boolean addWaiting;
	ArrayList<GameObject> removeQueue;
	boolean removeWaiting;
	boolean multiplayer;
	private boolean m_AcceleromterOn;

	public GameGraph() {
		graph = new ArrayList<GameObject>();
		addQueue = new ArrayList<GameObject>();
		removeQueue = new ArrayList<GameObject>();
	}

	public void add(GameObject a) {
		graph.add(a);
		lastObject++;
	}
	
	public void remove(GameObject a) {
		graph.remove(a);
	}
	
	public void addWhenPossible(GameObject o) {
		// adds a new game object to the gamegraph as soon as possible. This is
		// needed when you try to add something to the game graph when the simulation
		// system is already running, because adding whilst the gamegraph is being
		// iterated over will cause a ConcurrentModificationException. This will queue
		// the game object to be added, and the GameSimulationSystem will add it when
		// it is safe.
		addQueue.add(o);
		addWaiting = true;
		
	}
	
	public void removeWhenPossible(GameObject o) {
		lastObject--;
		removeQueue.add(o);
		removeWaiting = true;
	}

	
	public ArrayList<GameObject> getGraph() {
		return graph;
	}

	public GameObject getLastObject() {
		if(lastObject==-1) {
			return null;
		}
		return graph.get(lastObject);
	}
	public boolean isMultiplayer() {
		return multiplayer;
	}

	/**
	 * Visits every object in the game graph and tells it to perform its own
	 * flatten routine using the given stream_builder IFF object is modified
	 * since last send. Will also add the count
	 * of objects that are expected to be written, and the header for each object
	 * before it is written.
	 * 
	 * @param stream_builder
	 * @return Whether the flatten was succesful.
	 * @throws IOException
	 */
	public boolean flatten(ByteArrayOutputStream stream_builder) throws IOException {
		int modified_count = getModifiedCount();
		stream_builder.write(modified_count); 
		int obj_number = 1;
		for(GameObject obj : graph) {
			if(obj.modified) {
				//Log.d("GameGraph", "OBJ "+obj.id+" ("+obj.name+") MODIFIED");
				stream_builder.write(obj_number++);
				obj.flatten(stream_builder);
				obj.modified = false; // reset modified flag.
			}
		}
	//	Log.d("GameGraph", "tried to flatten stuff. obj_number = "+obj_number+", graphsize = "+graph.size());
		return obj_number-1==modified_count; 
	}

	/**
	 * Counts the number of objects within graph that have their modified
	 * flag set to true. 
	 * @return Number of modified objects in graph
	 */
	private int getModifiedCount() {
		int i = 0;
		for(GameObject obj : graph) {
			if(obj.modified)
				i++;
		}
		return i;
	}

	/**
	 * Expands the byte array b starting at index i. It will check the count of
	 * objects contained in the array, and create that many blank objects. Each
	 * blank object is then asked to expand itself, being given the buffer and an
	 * index to start expanding from.
	 * 
	 * @param b Byte array containing a flattened game graph
	 * @param i Index in array that has been already expanded
	 * @throws GameGraphException 
	 */
	public void expand(byte[] b, int i) throws GameGraphException {
		int expected_size = b[i++]; // this is the size of the graph as written at the 
									// start of the flatten function.
	//	Log.d("GameGraph", "     Expanding "+expected_size+" GameObjects");
		for(int j = 1; j <= expected_size; j++) {
			// read the object number and check it matches j - the object number we 
			// think we should be reading.
			if(b[i++]!=j) { throw new GameGraphException("GameObject numbers do not match - i = "+(i-1)+", j = "+j); }
			GameObject obj = new GameObject(this);
		//	Log.d("GameGraph", "     GameObject "+b[i-1]);
			i = obj.expand(b, i);
			add(obj);
		}
		
	}

	public void print() { 
		Log.d("GameGraph", "GRAPH::"+graph.toString());
		for(GameObject obj : graph) {
			Log.d("GameGraph", "     OBJ::"+obj.id+"::"+obj.m_Name);
			for(Component comp : obj.objectComponents) {
				Log.d("GameGraph", "          COMP::"+comp.id+"::"+comp.getType());
			}
		}
		
	}

	/**
	 * Searches through the gamegraph and finds a component with matching
	 * id to comp, and then replaces it with comp.
	 * @param comp
	 */
	public void replace(Component comp_new) {
		for(GameObject obj : graph) {
			for(Component comp_old : obj.objectComponents) {
			//	Log.d("GameGraph", "Comparing two components. Old: "+comp_old.toString()+" ("+comp_old.id+"). New: "+comp_new.toString()+"("+comp_new.id+")");
				if(comp_new.id==comp_old.id) {
					comp_new.parent = comp_old.parent;
					obj.removeComponent(comp_old);
					obj.addComponentWhenPossible(comp_new);
				}
			}
		}
	}

	/**
	 * Replaces members of the obj in gamegraph with members of obj.
	 * @param obj
	 */
	public void replace(GameObject obj_new) {
		for(GameObject obj_old : graph) {
			if(obj_new.id==obj_old.id) {
				obj_old.x = obj_new.x;
				obj_old.y = obj_new.y;
			}
		}
	}

	public void setMultiplayer(String str) {
		if(str == null || !str.equalsIgnoreCase("true")) {
			multiplayer = false;
		} else {
			multiplayer = true;
		}
	}

	/**
	 * Sets the m_AcceleromterOn to true if this game graph wants to use
	 * the acceleromter. 
	 * @param str
	 */
	public void enableAccelerometer(String str) {
		if(str == null || !str.equalsIgnoreCase("true")) {
			m_AcceleromterOn = false;;
		} else {
			m_AcceleromterOn = true;;
		}

		
	}

	public boolean accelerometerOn() {
		return m_AcceleromterOn;
	}


}
